package com.quwan.component;

import com.quwan.app.service.PrePaymentInterface;
import com.quwan.app.vo.MsgQueryParams;
import com.quwan.app.vo.SystemMsgResult;
import com.quwan.app.vo.account.BindAccParams;
import com.quwan.app.vo.pay.TransferParams;
import com.quwan.app.vo.pay.TransferResult;
import com.quwan.common.Check;
import com.quwan.common.enums.*;

import com.quwan.common.exception.ExpCode;
import com.quwan.common.result.PageResult;
import com.quwan.security.auth.vo.AppLoginParams;
import com.quwan.security.auth.vo.LoginResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;


/**
 * @author quan
 * @date 2021-12-08 23:59
 */

@Slf4j
@Component
public class ComponentFactory implements ApplicationContextAware, CommandLineRunner {
    private static final Integer INSTANCE_MAX = 500;
    private static final ConcurrentMap<String, Object> INSTANCE = new ConcurrentHashMap<>(32);
    private static final String GET_COMPONENT = "getComponent";
    private static final String GET_METHOD = "getMethod";
    private static ApplicationContext applicationContext;
    @Value("${listener.package}")
    private String packageName;

    //获取应用名称
    public String getApplicationName() {
        return applicationContext.getEnvironment().getProperty("spring.application.name");
    }

    /// 获取当前环境
    public String getActiveProfile() {
        return applicationContext.getEnvironment().getActiveProfiles()[0];
    }

    //获取支付实例
    public PrePaymentInterface getPayInstance(PaymentEnum payment) {
        PrePaymentInterface instance = null;
        return this.customInstance(payment.getClass(), payment.getMethod(), instance);
    }

    //获取转账实例
    public Function<TransferParams, TransferResult> getTransInstance(TransEnum trans) {
        Function<TransferParams, TransferResult> instance = null;
        return this.customInstance(trans.getClass(), trans.getMethod(), instance);
    }

    //获取登录实例
    public Function<AppLoginParams, LoginResult> getLoginInstance(LoginEnum login) {
        Function<AppLoginParams, LoginResult> instance = null;
        return this.customInstance(login.getClass(), login.getMethod(), instance);
    }


    // 绑定第三方平台账号 实例
    public Predicate<BindAccParams> getBindAccInstance(BindAccEnum bind) {
        Predicate<BindAccParams> instance = null;
        return this.customInstance(bind.getClass(), bind.getMethod(), instance);
    }

    // 获取 订单取消 redis key监听事件处理 实例
    public Consumer<String> getRedisListenerProcessInstance(RedisListenerEnum listener) {
        Consumer<String> instance = null;
        return this.customInstance(listener.getClass(), listener.getMethod(), instance);
    }

    //处理支付异步通知 实例  buyIt购买类型事件
    public Function<Map<String, String>, String> getNotifyInstance(PaymentEnum payment, String buyIt) {
        Function<Map<String, String>, String> instance = null;
        return this.customInstance(payment.getClass(), buyIt, instance);
    }

    //系统消息
    public Function<MsgQueryParams, PageResult<SystemMsgResult>> getSystemMsgInstance(SystemMsgEnum systemMsg) {
        Check.notNull(systemMsg,ExpCode.PARAMS_UNKNOWN);

        Function<MsgQueryParams, PageResult<SystemMsgResult>> instance = null;
        return this.customInstance(systemMsg.getDeclaringClass(), systemMsg.getMethod(), instance);
    }

    /**
     * 加入 INSTANCE （INSTANCE 不存在methodName 执行添加操作）
     * 枚举类常量 须满足2个字段 method(业务处理方法名) component(method定义的 spring容器注入对象 bean名称)
     *
     * @param enumClazz  Enum.class
     * @param methodName 方法名称 需要在 Enum.class枚举有常量值
     * @param t          返回实例
     * @param <T>        返回类型
     * @return
     */
    private <T> T customInstance(Class enumClazz, String methodName, T t) {
        //不存在才进行添加
        Object o = INSTANCE.get(methodName);
        if (o != null)
            return (T) o;

        log.info("create::{} Listener instance::[{}],current instance size::{}", enumClazz.getName(), methodName, INSTANCE.size());
        //当缓存的实例方法 大于设定的阈值 不进行添加
        if (INSTANCE.size() > INSTANCE_MAX)
            throw new RuntimeException("INSTANCE value max");
        //目前使用枚举类 如果不是 不进行操作
        if (!enumClazz.isEnum())
            throw new IllegalArgumentException(String.format("not support type::%s", enumClazz.getName()));

        Object getComponent = null;
        //拿到 该类下的所有 方法
        Method[] declaredMethods = enumClazz.getDeclaredMethods();

        //获取枚举构造器
        Object[] enumConstants = enumClazz.getEnumConstants();
        for (Object enumObj : enumConstants) {
            try {

                for (Method method : declaredMethods) {
                    if (!method.getName().startsWith("get") || method.getParameterCount() > 0)
                        continue;//只要无参的get方法

                    //查找跟入参方法名一样的枚举常量值 拿到组件名称
                    if (methodName.equalsIgnoreCase(String.valueOf(method.invoke(enumObj)))) {
                        //如果找到 就执行枚举类组件方法获取组件名称
                        getComponent = enumClazz.getDeclaredMethod(GET_COMPONENT).invoke(enumObj);
                        break;
                    }

                }
                if (getComponent == null)
                    continue;

                //spring容器拿到单例对象
                Object instanceComponent = applicationContext.getBean(String.valueOf(getComponent));

                //类反射 拿到方法
                Method instanceMethod = instanceComponent.getClass().getDeclaredMethod(methodName);
                instanceMethod.setAccessible(true);
                //执行
                T ret = (T) instanceMethod.invoke(instanceComponent);
                if (ret != null) {
                    if (INSTANCE.get(methodName) != null)
                        throw new IllegalArgumentException(String.format("INSTANCE %s已存在%s组件 或%s方法名称重复", methodName, getComponent, methodName));

                    INSTANCE.put(methodName, ret);
                    return ret;
                }
                log.error("{} not found method::{}", instanceComponent.getClass().getName(), instanceMethod.getName());

            } catch (IllegalAccessException e) {
                log.error("[{}]Access Exception check instance[{}]", methodName, enumObj.getClass().getName());
            } catch (InvocationTargetException e) {
                log.error("[{}]invoke exception check instance[{}]", methodName, methodName);
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                log.error("[{}]not found instance or[{}]not register[{}]", enumClazz.getName(), enumClazz.getName(), methodName);
                throw new RuntimeException(e);
            }
            log.error("{} not found value::{}", enumClazz.getName(), methodName);
        }
        log.error("customInstance register fail result will be null");
        return null;
    }


    /**
     * spring容器 初始化完成后
     * 进行一次 packageName 扫描
     * 根据枚举类定义的参数 符合条件的 通过反射把需要执行的表达式 放入ComponentFactory.INSTANCE
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(String... args) throws Exception {
        log.info("begin scan listener Enum package::{}", packageName);
        long begin = System.currentTimeMillis();
        if (StringUtils.isBlank(packageName))
            throw new IllegalArgumentException("listener package params null");

        //多个包路径逗号分割
        String[] split = packageName.split(",");
        //解析package 下所有的类路径
        PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
        CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
        ClassLoader loader = ClassUtils.getDefaultClassLoader();
        loader = loader == null ? getClass().getClassLoader() : loader;
        for (String pack : split) {
            //packageName 需要替换成 目录格式
            String formatPackage = pack.replaceAll("\\.", "/");
            Resource[] resources = pathMatchingResourcePatternResolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + formatPackage + "/**/*.class");
            for (Resource resource : resources) {
                //拿到每个类路径
                MetadataReader reader = cachingMetadataReaderFactory.getMetadataReader(resource);
                String className = reader.getClassMetadata().getClassName();
                //拿到字节码对象
                Class clazz = loader.loadClass(className);
                if (clazz.isEnum()) {
                    //目前 统一使用枚举类
                    Method component = null;
                    Method method = null;
                    for (Method methods : clazz.getDeclaredMethods()) {
                        //这里只能获取所有方法进行遍历筛选
                        //侦听参数要求 component method 2个参数必须
                        //如果直接使用方法名获取方法 该包下的class文件没有 component method属性会 抛出异常
                        if (methods.getName().equalsIgnoreCase(GET_COMPONENT))
                            component = methods;

                        if (methods.getName().equalsIgnoreCase(GET_METHOD))
                            method = methods;
                        if (component != null && method != null)
                            //已经找到了对应方法就不必再进行查找了
                            break;
                    }
                    if (component == null || method == null)
                        continue; //找不到定义的2个方法 检查的元素不符合条件 检查下一个元素

                    for (Object enums : clazz.getEnumConstants()) {
                        //遍历枚举类 取每个常量

                        //获取枚举常量的 component method 常量值
                        method.setAccessible(true);
                        component.setAccessible(true);
                        //拿到枚举常量值里面定义的具体参数
                        Object componentName = component.invoke(enums);
                        Object methodName = method.invoke(enums);
                        if (componentName != null && methodName != null) {
                            //只有 component method 存在值的情况下 才到spring容器获取bean
                            Object instanceComponent = applicationContext.getBean(String.valueOf(componentName));
                            try {
                                //通过class反射拿到 要执行的方法
                                Method componentMethod = instanceComponent.getClass().getDeclaredMethod(String.valueOf(methodName));
                                componentMethod.setAccessible(true);
                                //调用对应的方法
                                Object invoke = componentMethod.invoke(instanceComponent);
                                if (invoke != null)
                                    INSTANCE.put(methodName.toString(), invoke);
                                else
                                    throw new RuntimeException(String.format("invoke component:%s  method:%s  result:null", instanceComponent, componentMethod));
                            } catch (NoSuchMethodException e) {
                                log.error("[{}]NotFount Instance or [{}] not register[{}]", componentName, clazz.getName(), methodName);
                                throw new RuntimeException(e);
                            } catch (IllegalAccessException e) {
                                log.error("[{}]AccessException check Instance[{}]", componentName, methodName);
                                throw new RuntimeException(e);
                            } catch (InvocationTargetException e) {
                                log.error("[{}]invoke exception check Instance[{}]", componentName, methodName);
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }
            }
        }
        log.info("{}::mill Finish Scan Listener Enum Package::{}  Found Instance::{} ,Methods::{}", System.currentTimeMillis() - begin, packageName, INSTANCE.size(), INSTANCE.keySet().toString());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (ComponentFactory.applicationContext == null) {
            ComponentFactory.applicationContext = applicationContext;
        }
    }
}